package com.harboursoftware.xstorage.db.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.RowProcessor;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.harboursoftware.xstorage.XStorageException;
import com.harboursoftware.xstorage.ac.AccessControlPolicy;
import com.harboursoftware.xstorage.ac.CanonicalUserGrantee;
import com.harboursoftware.xstorage.ac.Grant;
import com.harboursoftware.xstorage.ac.Grantee;
import com.harboursoftware.xstorage.ac.GroupGrantee;
import com.harboursoftware.xstorage.db.BucketDao;
import com.harboursoftware.xstorage.model.XBucket;
import com.harboursoftware.xstorage.model.XUser;
import com.harboursoftware.xstorage.util.UUIDGenerator;

/**
 * {@link BucketDao} 关系数据库的实现.
 * <p>
 * 注意：级联关系由数据库处理,数据库设计文档参考/docs/db/*.
 * </p>
 * <b>此类是线程安全的.</b>
 * 
 * @author Simon Leung
 * @since 0.1
 */
public class BucketSqlDao implements BucketDao {
    private final static Logger log = LoggerFactory.getLogger(BucketSqlDao.class);
    public final static int GRANTEE_TYPE_CANONICAL_USER = 1;
    public final static int GRANTEE_TYPE_ALL_USER = 2;
    public final static int GRANTEE_TYPE_AUTHENTICATED_USER = 3;
    
    private DatabaseProvider databaseProvider;
    private RowProcessor bucketRowProcessor;
    private ResultSetHandler<XBucket> bucketBeanHandler;
    private ResultSetHandler<List<XBucket>> bucketBeanListHandler;
    private QueryRunner sqlRunner = new QueryRunner();
    
    public BucketSqlDao(DatabaseProvider databaseProvider) {
        this.databaseProvider = databaseProvider;
        bucketRowProcessor = new BucketRowProcessor(databaseProvider);
        bucketBeanHandler = new BeanHandler<XBucket>(XBucket.class, bucketRowProcessor);
        bucketBeanListHandler = new BeanListHandler<XBucket>(XBucket.class, bucketRowProcessor);
    }

    /**
     * @inheritDoc
     */
    @Override
    public XBucket findBucketByName(String name) throws XStorageException {
        Connection connection = getConnection();
        String sql = "SELECT * FROM x_bucket WHERE name=?;";
        try {
            XBucket bucket = sqlRunner.query(connection, sql, bucketBeanHandler, name);
            log.debug("find bucket by name[{}],result : {}", name, bucket);
            return bucket;
        } catch (SQLException e) {
            throw new XStorageException("fail to find bucket by name[" + name + "]", e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }

    /**
     * @inheritDoc
     */
    @Override
    public void saveBucket(XBucket bucket) throws XStorageException {
        log.debug("save bucket [{}]", bucket);
        Connection connection = getConnection();
        try {
            connection.setAutoCommit(false);

            saveBucket(connection, bucket);

            connection.commit();
            connection.setAutoCommit(true);
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException e1) {
                throw new XStorageException("fail to rollback", e);
            }
            throw new XStorageException("fail to save bucket to database", e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }

    /**
     * save bucket to database.
     */
    private void saveBucket(Connection connection, XBucket bucket) throws SQLException {
        String sql = "INSERT INTO `x_bucket` (`name`, `owner`) VALUES (?,?);";
        Object[] parameters = new Object[] { bucket.getName(), bucket.getOwner().getId() };
        sqlRunner.update(connection, sql, parameters);

        saveAcp(connection, bucket.getAcp(), bucket.getName());
    }

    /**
     * save bucket.acp to database.
     */
    private void saveAcp(Connection connection, AccessControlPolicy acp, String bucketName) throws SQLException {
        log.debug("save bucket[{}] acp [{}]", bucketName, acp);
        String sql =
            "INSERT INTO `x_access_control_policy` (`id`, `onwer_id`, `onwer_display_name`, `bucket`) VALUES (?,?,?,?);";
        String acpId = UUIDGenerator.generateUUID();
        XUser acpOwner = acp.getOwner();
        sqlRunner.update(connection, sql, acpId, acpOwner.getId(), acpOwner.getDisplayName(), bucketName);

        saveGrants(connection, acp.getAccessControlList(), acpId);
    }

    /**
     * save bucket.acp.grants to database.
     */
    private void saveGrants(Connection connection, List<Grant> grants, String acpId) throws SQLException {
        log.debug("save grants of acp[id={}], grants : {}", acpId, grants);
        String sql =
            "INSERT INTO x_grant (`id`, `type`, `grantee_id`, `grantee_display_name`, `premission`, `acp`) VALUES (?,?,?,?,?,?);";
        for (Grant grant : grants) {
            Grantee grantee = grant.getGrantee();
            int type = 1;
            String granteeId = null;
            String granteeDisplayName = null;
            if (grantee instanceof CanonicalUserGrantee) {
                type = GRANTEE_TYPE_CANONICAL_USER;
                CanonicalUserGrantee userGrantee = (CanonicalUserGrantee) grantee;
                granteeId = userGrantee.getId();
                granteeDisplayName = userGrantee.getDisplayName();
            } else if (grantee == GroupGrantee.ALL_USERS) {
                type = GRANTEE_TYPE_ALL_USER;
            } else if (grantee == GroupGrantee.AUTHENTICATED_USERS) {
                type = GRANTEE_TYPE_AUTHENTICATED_USER;
            }
            String grantId = UUIDGenerator.generateUUID();
            String permission = grant.getPermission().toString();
            sqlRunner.update(connection, sql, grantId, type, granteeId, granteeDisplayName, permission, acpId);
        }
    }

    @Override
    public void updateBucket(XBucket bucket) throws XStorageException {
        log.debug("update bucket [{}]", bucket);
        Connection connection = getConnection();
        String sql = null;
        try {
            connection.setAutoCommit(false);
            String bucketName = bucket.getName();
            // update bucket
            sql = "UPDATE x_bucket SET owner=? WHERE name=?;";
            sqlRunner.update(connection, sql, bucket.getOwner().getId(), bucketName);

            // delete old acp
            sql = "DELETE FROM x_access_control_policy WHERE bucket=?;";
            sqlRunner.update(connection, sql, bucketName);

            // save new acp
            saveAcp(connection, bucket.getAcp(), bucketName);
            connection.commit();
            connection.setAutoCommit(true);  
        } catch (SQLException e) {
            log.error("fail to update bucket, {}", bucket);
            try {
                connection.rollback();
            } catch (SQLException e1) {
                throw new XStorageException("fail to rollback", e);
            }
            throw new XStorageException("fail to update bucket[" + bucket + "]", e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }

    @Override
    public void deleteBucket(XBucket bucket) throws XStorageException {
        Connection connection = getConnection();
        String sql = "DELETE FROM x_bucket WHERE name=?;";
        String bucketName = bucket.getName();
        log.debug("delete bucket[{}]", bucketName);
        try {
            int updatedRows = sqlRunner.update(connection, sql, bucketName);
            if (updatedRows == 0) {
                throw new XStorageException("bucket '" + bucketName + "' does not exists");
            }
        } catch (SQLException e) {
            throw new XStorageException("fail to deletet bucket[" + bucketName + "]", e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }
    
    private Connection getConnection() throws XStorageException {
        try {
            return databaseProvider.getConnection();
        } catch (SQLException e) {
            throw new XStorageException("fail to get database connection", e);
        }
    }

    @Override
    public List<XBucket> findBucketsOfUser(XUser user) throws XStorageException {
        if(user == null || user.getId() == null) {
            throw new IllegalArgumentException("the user and user.id can't be null");
        }
        Connection connection = getConnection();
        String sql = "SELECT * FROM x_bucket WHERE owner=?";
        try {
            List<XBucket> buckets = sqlRunner.query(connection, sql, bucketBeanListHandler, user.getId());
            log.debug("find buckets of user[{}],result : {}", user.getId(), buckets);
            return buckets;
        } catch (SQLException e) {
            throw new XStorageException("fail to find bucket of user[" + user.getId() + "]", e);
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }
}
